package cn.ac.iie.kgmanager.dao;

import ch.qos.logback.core.encoder.EchoEncoder;
import cn.ac.iie.kgmanager.domain.Node;
import cn.ac.iie.kgmanager.dto.Page;
import lombok.extern.slf4j.Slf4j;
import org.neo4j.driver.v1.*;
import org.neo4j.driver.v1.types.Path;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.sql.PreparedStatement;
import java.util.*;

@Slf4j
@Repository
public class NodeDao {
    public Object getValues;
    @Autowired
    Driver driver;

    /**
     * 功能描述: 批量添加节点
     *
     * @Param: [nodes]
     * @Return: boolean
     * @Author: syh
     * @Date: 2019/6/14 19:55
     */
    public boolean addNodes(List<Node> nodes) {

        //创建整个语句
        StringBuffer sbCypher = new StringBuffer();
        //字符拼接
        sbCypher.append("create ");

        //创建List用于接收单个节点的信息
        List<String> nodeList = new LinkedList<>();
        //遍历数组内所有节点
        for (int i = 0; i < nodes.size(); i++) {
            //创建节点子语句，用于拼凑单个节点信息
            StringBuffer sbNodes = new StringBuffer();
            //字符拼接
            sbNodes.append("(m" + i);
            //获取单个节点信息
            Node node = nodes.get(i);
            //获取改节点内所有标签信息
            Set<String> labels = node.getLabels();
            //拼接所有标签信息
            for (String label : labels) {
                sbNodes.append(":" + label);
            }
            //字符拼接，准备拼接属性信息
            sbNodes.append("{");

            //将生成好的ID和节点名称放入属性列表中一起拼接
            String nodeId = node.getId();
            String nodeName = node.getName();

            node.addProperty("id", nodeId);
            node.addProperty("name", nodeName);

            //获取该节点所有属性信息
            Map<String, Object> nodeProperties = node.getProperties();
            //创建List用于接收拼接好的属性信息
            List<String> propertiesList = new LinkedList<>();
            //遍历节点内所有属性
            for (Map.Entry<String, Object> entry : nodeProperties.entrySet()) {
                //创建属性子语句，用于拼接节点内所有属性信息
                StringBuffer sbProperties = new StringBuffer();
                //获取属性kv
                String key = entry.getKey();
                Object value = entry.getValue();
                //如果值为字符串则添加单引号，其余直接拼接
                if (value instanceof String) {
                    sbProperties.append(key + ":'" + value + "'");
                } else {
                    sbProperties.append(key + ":" + value);
                }

                //将所有属性放入属性List中
                propertiesList.add(sbProperties.toString());
            }

            //用逗号将所有属性分割，并放入节点信息中
            sbNodes.append(String.join(", ", propertiesList));

            //字符拼接
            sbNodes.append("}");

            sbNodes.append(")");

            //将节点属性放入List中
            nodeList.add(sbNodes.toString());
        }

        //用逗号分割，将所有节点信息进行拼接，放入语句中
        sbCypher.append(String.join(",", nodeList));

        String cypher = sbCypher.toString();
        log.info(cypher);

        return runCypher(cypher);
    }

    /**
     * 功能描述: 批量删除节点
     *
     * @Param: [ids]
     * @Return: boolean
     * @Author: syh
     * @Date: 2019/6/14 19:59
     */
    public boolean deleteNodes(List<String> ids) {

        //准备拼接语句
        StringBuffer sbCypher = new StringBuffer();

        sbCypher.append("match ");
        //建立List接受匹配信息和删除节点信息
        List<String> idList = new LinkedList<>();
        List<String> mList = new LinkedList<>();
        //遍历idList每一个元素
        for (int i = 0; i < ids.size(); i++) {
            //遍历match所有id相关的节点
            StringBuffer sbId = new StringBuffer();
            String id = ids.get(i);
            //拼接语句
            sbId.append("(m" + i + "{id:'" + id + "'})");
            idList.add(sbId.toString());

            //遍历所有的id，用于删除
            StringBuffer mId = new StringBuffer();
            mId.append("m" + i);
            mList.add(mId.toString());
        }
        //拼接idList所有元素和对应id
        sbCypher.append(String.join(", ", idList));

        //删除所有节点以及联系
        sbCypher.append(" detach delete ");

        //拼接所有id，用于删除
        sbCypher.append(String.join(",", mList));

        System.out.println(sbCypher.toString());

        String cypher = sbCypher.toString();
        log.info(cypher);

        return runCypher(cypher);
    }

    /**
     * 功能描述: 批量更新节点内容，根据唯一id进行匹配
     *
     * @Param: [nodes, preNodeInfo]
     * @Return: boolean
     * @Author: syh
     * @Date: 2019/6/14 20:27
     */
    public boolean updateNodes(List<Node> nodes, Map<String, Set<String>> preNodeInfo) {

        //初始化语句
        StringBuffer sbCypher = new StringBuffer();

        //建立list用于拼接语句，分成3部分，match、removeSet（修改标签）、set（修改属性）
        List<String> matchList = new LinkedList<>();
        List<String> removeSetList = new LinkedList<>();
        List<String> setList = new LinkedList<>();

        //遍历传入所有节点信息
        for (int i = 0; i < nodes.size(); i++) {

            //初始化子语句，用于拼接
            StringBuffer sbMatch = new StringBuffer();
            StringBuffer sbRemoveSet = new StringBuffer();
            StringBuffer sbSet = new StringBuffer();

            //节点match部分

            Node node = nodes.get(i);
            //将生成好的ID和节点名称放入属性列表中
            String nodeId = node.getId();
            String nodeName = node.getName();
            node.addProperty("id", nodeId);
            node.addProperty("name", nodeName);

            //拼接名称以及对应label，label用id查询获取
            Set<String> preLabels = preNodeInfo.get(nodeId);
            sbMatch.append("(m" + i);
            System.out.println(preLabels);
            for (String preLabel : preLabels) {
                sbMatch.append(":" + preLabel);
            }
            sbMatch.append("{id:'" + nodeId + "'})");
            matchList.add(sbMatch.toString());

            //节点remove set部分

            sbRemoveSet.append(" remove m" + i);
            for (String preLabel : preLabels) {
                sbRemoveSet.append(":" + preLabel);
            }
            sbRemoveSet.append(" set m" + i);
            Set<String> nodeLabels = node.getLabels();
            for (String nodeLabel : nodeLabels) {
                sbRemoveSet.append(":" + nodeLabel);
            }
            removeSetList.add(sbRemoveSet.toString());

            //节点set部分
            Map<String, Object> nodeProperties = node.getProperties();

            for (Map.Entry<String, Object> entry : nodeProperties.entrySet()) {
                //获取属性kv
                String key = entry.getKey();
                Object value = entry.getValue();
                //如果值为字符串则添加单引号，其余直接拼接
                if (value instanceof String) {
                    sbSet.append(" set m" + i + "." + key + "='" + value + "'");
                } else {
                    sbSet.append(" set m" + i + "." + key + "=" + value);
                }
                //将所有属性放入属性List中
                setList.add(sbSet.toString());
            }

        }
        sbCypher.append("match ");
        //拼接match部分
        sbCypher.append(String.join(",", matchList));
        //拼接remove set部分
        sbCypher.append(String.join(" ", removeSetList));
        //拼接set部分
        sbCypher.append(String.join(" ", setList));

        String cypher = sbCypher.toString();
        log.info(cypher);

        return runCypher(cypher);
    }

    /**
     * 功能描述: 根据id返回节点信息
     *
     * @Param: [id]
     * @Return: cn.ac.iie.kgmanager.domain.Node
     * @Author: syh
     * @Date: 2019/6/14 20:28
     */
    public Node getNodeById(String id) {
        //初始化拼接语句
        StringBuffer sbCypher = new StringBuffer();
        //根据id拼接查询语句
        sbCypher.append("match (m) where m.id='" + id + "' return m");
        //完成语句拼接，转换至String
        String cypher = sbCypher.toString();
        log.info(cypher);

        Node node = new Node();
        //连接neo4j
        try {
            Session session = driver.session();
            StatementResult Result = session.run(cypher);

            List<Value> values = getValues(Result);
            //遍历Values
            for (Value value : values) {
                node = valueToNodes(value);
            }
            System.out.println(node);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //移除name and id from properties
        node.getProperties().remove("name");
        node.getProperties().remove("id");
        return node;
    }


    /**
     * 功能描述: 根据名称和标签模糊查询节点信息，分页查询
     *
     * @Param: [pageNum, pageSize, labels, name]
     * @Return: cn.ac.iie.kgmanager.dto.Page
     * @Author: syh
     * @Date: 2019/6/14 20:29
     */
    public Page getNodeLikeName(Integer pageNum, Integer pageSize, Set<String> labels, String name) {

        //初始化拼接语句
        StringBuffer sbCypher = new StringBuffer();
        //拼接match
        sbCypher.append("match (m");
        //循环标签
        for (String label : labels) {
            sbCypher.append(":" + label);
        }
        sbCypher.append(") where m.name =~ '.*" + name + ".*' ");

        sbCypher.append("return distinct m ");

        //分页
        sbCypher.append("skip " + (pageNum - 1) * pageSize + " limit " + pageSize);

        String cypher = sbCypher.toString();
        log.info(cypher);

        Page T = new Page();

        Session session = driver.session();
        StatementResult Result = session.run(cypher);
        Node node = new Node();
        List<Value> values = getValues(Result);
        ArrayList list = new ArrayList();

        for (Value value : values) {
            node = valueToNodes(value);
            System.out.println(node);
            list.add(node);
        }

        System.out.println(list);
        T.setPageNum(pageNum);
        T.setPageSize(pageSize);
        T.addAll(list);
        System.out.println(T);

        return (T);
    }

    /**
     * 功能描述: 根据标签和名称查询节点信息，节点唯一
     *
     * @Param: [labels, name]
     * @Return: cn.ac.iie.kgmanager.domain.Node
     * @Author: syh
     * @Date: 2019/6/14 20:30
     */
    public Node getNodeByName(Set<String> labels, String name) {

        Session session = driver.session();

        //根据具体名称和标签查询节点
        StringBuffer sbCypher = new StringBuffer();
        sbCypher.append("match (m");
        for (String label : labels) {
            sbCypher.append(":" + label);
        }
        sbCypher.append(") where m.name ='" + name + "' return m");
        String cypher = sbCypher.toString();
        log.info(cypher);

        Node node = null;
        try {
            StatementResult sr = session.run(cypher);
            List<Value> values = getValues(sr);
            for (Value value : values) {
                node = valueToNodes(value);
            }
            System.out.println(node);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return node;
    }


    /**
     * 功能描述: runCypher，返回true、false
     *
     * @Param: [cypher]
     * @Return: boolean
     * @Author: syh
     * @Date: 2019/6/14 20:31
     */
    private boolean runCypher(String cypher) {
        try (Session session = driver.session()) {
            session.run(cypher);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }


    /**
     * 功能描述: 遍历一个value的属性，存放至node中，node格式详见domain.Node
     *
     * @Param: [value]
     * @Return: cn.ac.iie.kgmanager.domain.Node
     * @Author: syh
     * @Date: 2019/6/14 20:31
     */
    private Node valueToNodes(Value value) {
        org.neo4j.driver.v1.types.Node node = value.asNode();
        Node node2 = new Node();

        Set<String> labels = new HashSet<>();

        for (String label : node.labels()) {
            labels.add(label);
        }

        node2.setLabels(labels);

        String id = node.get("id").asString();
        String name = node.get("name").asString();
        HashMap<String, Object> properties = new HashMap<>();
        properties.putAll(node.asMap());

        node2.setId(id);
        node2.setName(name);
        node2.setProperties(properties);

        return node2;
    }


    /**
     * 功能描述: 根据从neo4j取回的信息，遍历存放至values中
     *
     * @Param: [sr]
     * @Return: java.util.List<org.neo4j.driver.v1.Value>
     * @Author: syh
     * @Date: 2019/6/14 20:32
     */
    public List<Value> getValues(StatementResult sr) {
        List<Value> valueArrayList = new ArrayList<>();

        while (sr.hasNext()) {
            Record record = sr.next();
            List<Value> values = record.values();
            if (values == null || values.size() <= 0) {
                return null;
            } else {
                valueArrayList.addAll(values);
            }
        }
        return valueArrayList;
    }

    /**
     * 功能描述: 附属挂件方法，根据模糊名称查询totalSizeNumber，用于返回全部值
     *
     * @Param: [labels, name]
     * @Return: java.lang.Integer
     * @Author: syh
     * @Date: 2019/6/14 20:33
     */
    public Integer getTotalNumByLikeName(Set<String> labels, String name) {
        //初始化拼接语句
        StringBuffer sbCypher = new StringBuffer();
        //拼接match
        sbCypher.append("match (m");
        //循环标签
        for (String label : labels) {
            sbCypher.append(":" + label);
        }
        sbCypher.append(") where m.name =~ '.*" + name + ".*' ");

        sbCypher.append("return count (m)");

        return getTotalSizeValueByCypher(sbCypher);
    }

    /**
     * 功能描述: 中间方法
     *
     * @Param: [sourceId, labels, depth]
     * @Return: java.lang.Integer
     * @Author: syh
     * @Date: 2019/6/14 20:34
     */
    public Integer getTotalSizeByGetAroundNodes(String sourceId, Set<String> labels, Integer depth) {
        StringBuffer sbCypher = new StringBuffer();
        List<String> labelAdd = new LinkedList<>();
        List<Node> list = new LinkedList<>();

        //拼接match;
        getAroundNodesPre(sourceId, labels, depth, sbCypher, labelAdd);

        sbCypher.append(") and m.id='" + sourceId + "' return count (n) ");

        return getTotalSizeValueByCypher(sbCypher);
    }

    /**
     * 功能描述: 子系统，同上，获取totalSize值
     *
     * @Param: [sbCypher]
     * @Return: java.lang.Integer
     * @Author: syh
     * @Date: 2019/6/14 20:34
     */
    private Integer getTotalSizeValueByCypher(StringBuffer sbCypher) {
        String cypher = sbCypher.toString();
        log.info(cypher);

        Session session = driver.session();
        StatementResult Result = session.run(cypher);

        List<Value> values = getValues(Result);
        int totalSize = values.get(0).asInt();

        return totalSize;
    }


    /**
     * 功能描述: 语句拼接前半部分，属于周围节点查询方法
     *
     * @Param: [sourceId, labels, depth, sbCypher, labelAdd]
     * @Return: void
     * @Author: syh
     * @Date: 2019/6/14 20:35
     */
    private void getAroundNodesPre(String sourceId, Set<String> labels, Integer depth, StringBuffer sbCypher, List<String> labelAdd) {
        sbCypher.append("match (m)-[*1.." + depth + "]-(n) where (");
        for (String label : labels) {
            StringBuffer sbLabels = new StringBuffer();
            sbLabels.append("n:" + label);
            labelAdd.add(sbLabels.toString());
        }
        sbCypher.append(String.join(" or ", labelAdd));

        StringBuffer countSearch = sbCypher;
    }

    /**
     * 功能描述: 周围节点查询方法，根据id，层级，标签查询周围节点，支持分页
     *
     * @Param: [sourceId, labels, depth, pageNum, pageSize]
     * @Return: cn.ac.iie.kgmanager.dto.Page
     * @Author: syh
     * @Date: 2019/6/14 20:35
     */
    public Page getAroundNodes(String sourceId, Set<String> labels, Integer depth, Integer pageNum, Integer pageSize) {
        //初始化拼接语句
        StringBuffer sbCypher = new StringBuffer();
        List<Node> list = new LinkedList<>();
        List<String> labelAdd = new LinkedList<>();

        //拼接match;
        getAroundNodesPre(sourceId, labels, depth, sbCypher, labelAdd);

        sbCypher.append(") and m.id='" + sourceId + "' return n ");
        //分页
        if(pageNum != null && pageSize != null) {
            sbCypher.append("skip " + (pageNum - 1) * pageSize + " limit " + pageSize);
        }

        String cypher = sbCypher.toString();
        log.info(cypher);

        Session session = driver.session();
        Page T = new Page();

        StatementResult Result = session.run(cypher);
        Node node3 = new Node();
        List<Value> values = getValues(Result);
        ArrayList list2 = new ArrayList();
        for (Value value : values) {
            node3 = valueToNodes(value);
            list2.add(node3);
        }
        if(pageNum != null && pageSize != null) {
            T.setPageNum(pageNum);
            T.setPageSize(pageSize);
        }
        T.addAll(list2);
        System.out.println(T);
        return (T);
    }

    // 适配土拨鼠们的需求
    public List<Node> getLevelsNodes(String sourceId, Set<String> firstLabels, Set<String> secondLabels) {
        List<Node> nodes = new LinkedList<>();
        // match (n)--(m) where n.id = '' and (m:label1 or m:label2) with distinct m match p = (m)--(o) return distinct p
        StringBuffer sbCypher = new StringBuffer();
        sbCypher.append("match (n)--(m) where n.id = '" + sourceId + "' ");

        if (firstLabels != null && !firstLabels.isEmpty()) {
            sbCypher.append("and ");
            List<String> firstLabelCondition = new LinkedList<>();
            for (String label : firstLabels) {
                firstLabelCondition.add("m:" + label);
            }
            sbCypher.append("(" + String.join(" or ", firstLabelCondition) + ") ");
        }

        sbCypher.append("with distinct m ");

        sbCypher.append("match (m)--(o) ");
        if (secondLabels != null && !secondLabels.isEmpty()) {
            sbCypher.append("where ");
            List<String> secondLabelCondition = new LinkedList<>();
            for (String label : secondLabels) {
                secondLabelCondition.add("o:" + label);
            }
            sbCypher.append("(" + String.join(" or ", secondLabelCondition) + ") ");
        }


        sbCypher.append("return m, o");

        String cypher = sbCypher.toString();


        log.info(cypher);

        try {
            Session session = driver.session();

            StatementResult sr = session.run(cypher);

            while (sr.hasNext()) {
                Record record = sr.next();

                List<Value> values = record.values();

                for (Value value : values) {
                    Node node = valueToNodes(value);
                    nodes.add(node);
                }

            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return nodes;
    }


}